package top.itbook.util;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;

/**
 * 消息摘要算法安全 - 单向散列函数(Hash) - MD5算法
 * 本类采用懒汉单例模式 - 调用getInstance方法获取本类实例
 * 散列算法介绍（语文介绍水平不好，则直接复制百科内容）
 * 散列函数（或散列算法，又称哈希函数，英语：Hash Function）
 * 是一种从任何一种数据中创建小的数字“指纹”的方法。
 * 散列函数把消息或数据压缩成摘要，使得数据量变小，将数据的格式固定下来。
 * 该函数将数据打乱混合，重新创建一个叫做散列值（hash values，hash codes，hash sums，或hashes）的指纹。
 * 散列值通常用一个短的随机字母和数字组成的字符串来代表。
 * 算法重点：好的散列函数在输入域中很少出现散列冲突。
 * 在散列表和数据处理中，不抑制冲突来区别数据，会使得数据库记录更难找到。
 */
public class MD5 extends Provider {
    private static MD5 instance; // 本类对象

    private final static String salt = "@KingMD5.top"; // 盐值

    /**
     * 构造器
     * @param name 提供者名称
     * @param versionStr 算法版本
     * @param info 算法信息
     */
    private MD5(String name,String versionStr,String info){
        super(name,versionStr,info); // 调用父类构造器
    }

    /**
     * 获取本类实例化对象
     * 若已经创建过则返回上一次创建的对象地址，若是第一次创建则本方法自动创建本类实例化对象并返回。
     * @return MD5类对象
     */
    public static MD5 getInstance(){
        if (instance == null){
            instance = new MD5("King","0.2","MD5信息摘要加密算法，该算法进行了加盐处理，这将会使彩虹表在盐值没有被暴露的情况下失效。");
        }
        return instance;
    }

    /**
     * MD5信息摘要算法 - 不可逆加密 - 字符串加密
     * @param content 要获取摘要的字符串
     * @return 若成功获取摘要则返回十六进制字符串，若方法发生不可预计的错误导致方法中止则返回null
     */
    @Deprecated
    public String md5encryption(String content){
        MessageDigest messageDigest;
        String s = null; // 加密后的十六进制字符串
        try {
            messageDigest = MessageDigest.getInstance("MD5"); // 获取实例化对象
            // 更新摘要
            messageDigest.update(content.getBytes());
            // 获取数据的信息摘要 - 进行哈希计算
            byte[] resultBytes = messageDigest.digest();
            // 将信息转换为十六进制字符串
            s = convertString(resultBytes);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return s;
    }

    /**
     * MD5信息摘要算法 - 加盐
     * 不可逆加密，字符串加密
     * @param content 要获取摘要的字符串
     * @return 若成功获取摘要则返回十六进制字符串，若方法发生不可预计的错误导致方法中止则返回null
     */
    public String md5encryptionPLUS(String content){
        MessageDigest messageDigest;
        String s = null; // 加密后的十六进制字符串
        try {
            messageDigest = MessageDigest.getInstance("MD5"); // 获取实例化对象
            // 将信息与盐值字符串进行拼接
            content = content + salt;
            // 更新摘要
            messageDigest.update(content.getBytes());
            // 获取数据的信息摘要 - 进行哈希计算
            byte[] resultBytes = messageDigest.digest();
            // 将信息转换为十六进制字符串
            s = convertString(resultBytes);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return s;
    }

    // 通常把加密后用16进制表示，那么64byte实际经过加密后长度有多少位数字呢？ -- 64 ÷ 4 = 8位
    // MD5默认 128 ÷ 4 = 32
    /**
     * 将字节数组转换为十六进制字符串 - 仅为此类提供工具方法，切勿暴力反射获取，否则将对系统速度造成影响
     * @param bytes 字节数组
     * @return 十六进制字符串
     */
    private String convertString(byte[] bytes){
        StringBuilder stringBuffer = new StringBuilder(bytes.length * 2);
        // 使用string的format方法进行转换
        for (byte aByte : bytes) {
            stringBuffer.append(String.format("%02x", aByte & 0xff));
        }
        return stringBuffer.toString();
    }
}
